#include "precompiled.h"
#include "ParticleManager.h"
#include "RenderManager.h"
#include "PhysicsManager.h"
#include "Sound_Manager.h"

//...................................................
#include "tbb/concurrent_hash_map.h"
#include "tbb/concurrent_queue.h"
using namespace tbb;
//...................................................


namespace ParticleManager{
	// A map of pools and their key names. All existing pools.
	concurrent_hash_map<std::string, ParticlePool> ppool_32, ppool_128, ppool_256, ppool_512;
	typedef concurrent_hash_map<std::string, ParticlePool>::accessor particle_map_accessor;
	typedef concurrent_hash_map<std::string, ParticlePool>::const_accessor particle_map_constant_accessor;
	typedef concurrent_hash_map<std::string, ParticlePool>::iterator particle_map_iterator;


	// A map of living pools only.
	std::vector<ParticlePool*> lppool_32, lppool_128, lppool_256, lppool_512;

	void Initialize(){}

	void birth_particles( Particle* ptr_p, int num, ParticleBehavior behavior ){
		bool didSound = false;
		for( int c = 0; c < num; c++ ){
			if( !ptr_p[c].alive ){
				if( !didSound ){
					didSound=true;
					SoundManager::OrderASound( "shot_fired", false );
				}
				ptr_p[c].alive = true;
				ptr_p[c].alpha=1.0f;
				ptr_p[c].velocity = 0.0f;
				ptr_p[c].pos = behavior.origin;

				ptr_p[c].origin = behavior.origin;
				ptr_p[c].start = behavior.origin;
				ptr_p[c].end = behavior.end;

				// Add some jitter to the velocity.
				float noise = ( behavior.noisy ) ? glm::compRand1(0.0f,2.0f) : 0.0f;
				noise *= behavior.acceleration;
				ptr_p[c].acceleration =  behavior.acceleration + noise;

				if( behavior.physical ){
					PhysicsManager::add_bulletPhantom_requests( ptr_p[c].phantom, &ptr_p[c] );
				}			
			}			
		}
	}
	void birth_particles_start( Particle* ptr_p, int num, ParticleBehavior behavior ){
		for( int c = 0; c < num; c++ ){

			ptr_p[c].alive = true;
			ptr_p[c].alpha=1.0f;
			ptr_p[c].velocity = 0.0f;
			ptr_p[c].pos = behavior.origin;

			ptr_p[c].origin = behavior.origin;
			ptr_p[c].start = behavior.origin;
			ptr_p[c].end = behavior.end;

			// Add some jitter to the velocity.
			float noise = ( behavior.noisy ) ? glm::compRand1(0.0f,2.0f) : 0.0f;
			noise *= behavior.acceleration;
			ptr_p[c].acceleration =  behavior.acceleration + noise;

			if( behavior.physical ){
				ptr_p[c].phantom = 0;
				PhysicsManager::add_bulletPhantom_requests( ptr_p[c].phantom, &ptr_p[c] );
			}			
		}
	}
	void kill_particles( Particle* ptr_p, int num ){
		for( int c = 0; c < num; c++ ){
			ptr_p[c].alive = false;
			ptr_p[c].alpha=0.0f;
		}
	}

	void CreateEmitter( PoolSize size, ParticleBehavior behavior, std::string key  ){
		ParticlePool p;
		p.pool32 = 0;
		p.pool128 = 0;
		p.pool256 = 0;
		p.pool512 = 0;

		p.key=key;

		particle_map_accessor a;

		switch( size ){
		case PS32:
			p.pool32 = new ParticlePool_32();
			p.pool32->behavior = behavior;
			p.size = PS32;
			birth_particles_start( p.pool32->particle, 32, behavior );

			// Map the key and particle.
			ppool_32.insert( a, key );
			a->second = p;
			break;

		case PS128:
			p.pool128 = new ParticlePool_128();
			p.pool128->behavior = behavior;
			p.size = PS128;
			birth_particles_start( p.pool32->particle, 128, behavior );

			ppool_128.insert( a, key );
			a->second = p;
			break;

		case PS256:
			p.pool256 = new ParticlePool_256();
			p.pool256->behavior = behavior;
			p.size = PS256;
			birth_particles_start( p.pool32->particle, 256, behavior );

			ppool_256.insert( a, key );
			a->second = p;
			break;

		case PS512:
			p.pool512 = new ParticlePool_512();
			p.pool512->behavior = behavior;
			p.size = PS512;
			birth_particles_start( p.pool32->particle, 512, behavior );

			ppool_512.insert( a, key );
			a->second = p;
			break;

		// Default to size 32.
		default:
			p.pool32 = new ParticlePool_32();
			p.pool32->behavior = behavior;
			p.size = PS32;
			birth_particles_start( p.pool32->particle, 32, behavior );

			ppool_32.insert( a, key );
			a->second = p;
			break;
		}
	}
	void DestroyPool( concurrent_hash_map<std::string, ParticlePool> &ppool ){
		for( particle_map_iterator it = ppool.begin(); it != ppool.end(); it++ ){
			if( it->second.size == PS32 )
				delete it->second.pool32;
			else if(it->second.size == PS128 )
				delete it->second.pool128;
			else if(it->second.size == PS256 )
				delete it->second.pool256;
			else
				delete it->second.pool512;
		}
		ppool.clear();
	}
	void DestroyPools(){
		DestroyPool( ppool_32 );
		DestroyPool( ppool_128 );
		DestroyPool( ppool_256 );
		DestroyPool( ppool_512 );
	}


	struct FindByKey {
		std::string key;
		FindByKey(const std::string& key) : key(key) {}
		bool operator()(const ParticlePool* p) { return p->key == key; }
	};

	bool poolContains( std::vector<ParticlePool*>& p, std::string key){
		std::vector<ParticlePool*>::iterator it = std::find_if(p.begin(), p.end(), FindByKey( key ) );

		if(it != p.end())
			return true;

		return false;
	}

	void RunParticles_Restart( std::string key  ){
		// Find the pool. Add it to the alive list.
		particle_map_accessor a;

		if( ppool_32.find( a, key ) ){
			birth_particles( a->second.pool32->particle, a->second.size, a->second.pool32->behavior  );
			lppool_32.push_back( &a->second );
		}
		else if( ppool_128.find( a, key ) ){
			birth_particles( a->second.pool128->particle, a->second.size, a->second.pool128->behavior );
			lppool_128.push_back( &a->second );
		}
		else if( ppool_256.find( a, key ) ){
			birth_particles( a->second.pool256->particle, a->second.size, a->second.pool256->behavior  );
			lppool_256.push_back( &a->second );
		}
		else if( ppool_512.find( a, key ) ){
			birth_particles( a->second.pool512->particle, a->second.size, a->second.pool512->behavior  );
			lppool_512.push_back( &a->second );
		}
	}
	void RunParticles( std::string key  ){
		// Find the pool. Add it to the alive list.
		particle_map_accessor a;

		if( ppool_32.find( a, key ) ){
			birth_particles( a->second.pool32->particle, 32, a->second.pool32->behavior );
			if( !poolContains( lppool_32, key ) ){
				lppool_32.push_back( &a->second );
			}
			//printf("lpool32 size %d\n",lppool_32.size() );
		}
		else if( ppool_128.find( a, key ) ){
			birth_particles( a->second.pool32->particle, 128, a->second.pool128->behavior );
			lppool_128.push_back( &a->second );
		}
		else if( ppool_256.find( a, key ) ){
			birth_particles( a->second.pool32->particle, 256, a->second.pool256->behavior );
			lppool_256.push_back( &a->second );
		}
		else if( ppool_512.find( a, key ) ){
			birth_particles( a->second.pool32->particle, 512, a->second.pool512->behavior );
			lppool_512.push_back( &a->second );
		}
	}

	void UpdateParticles_StartEnd( std::string key, glm::vec3 start, glm::vec3 end  ){

		// Find the pool. Change it's start/end.
		particle_map_accessor a;

		if( ppool_32.find( a, key ) ){
			a->second.pool32->behavior.origin = start;
			a->second.pool32->behavior.start = start;
			a->second.pool32->behavior.end = end;
		}
		else if( ppool_128.find( a, key ) ){
			a->second.pool128->behavior.origin = start;
			a->second.pool128->behavior.start = start;
			a->second.pool128->behavior.end = end;
		}
		else if( ppool_256.find( a, key ) ){
			a->second.pool256->behavior.origin = start;
			a->second.pool256->behavior.start = start;
			a->second.pool256->behavior.end = end;
		}
		else if( ppool_512.find( a, key ) ){
			a->second.pool512->behavior.origin = start;
			a->second.pool512->behavior.start = start;
			a->second.pool512->behavior.end = end;
		}
	}

	void Update_Linear( GLdouble time,  Particle* ptr_p, Vertex_P* pos_buff, int num, int* numAlive, ParticleBehavior behavior ){		
		// Track how many of these ladies are living.
		*numAlive=num;


		for( int c = 0; c < num; c++ ){
			// This particles distance from the start.
			float a_to_b = glm::distance( ptr_p[c].start, ptr_p[c].end );

			// Only Update living particles.
			if( ptr_p[c].alive ){
				// Update velocity.
				ptr_p[c].velocity += (ptr_p[c].acceleration) * time;
				ptr_p[c].velocity = glm::min( ptr_p[c].velocity, behavior.maxVelocity );

				// Translate in the direction.
				ptr_p[c].pos += (ptr_p[c].end - ptr_p[c].start) * ptr_p[c].velocity;
				
				// Mirror the change to the position buffer.
				pos_buff[c] = ptr_p[c].pos;

				// Kill the particle if it's past the end point.
				float a_to_c = glm::distance( ptr_p[c].start , ptr_p[c].pos);
				ptr_p[c].alive = ( a_to_b - a_to_c > 0 )?true:false;

				// If your dead or done or haven't started your still.
				if( !ptr_p[c].alive ){
					ptr_p[c].velocity=0.0f;
					ptr_p[c].alpha=0.0f;
					//printf("particle %d died\n", c);
				}
			}
			else
				*numAlive--;
		}

		// Note how we didn't change the particle rotation or anything.
	}
	void Update_Orbit( GLdouble time,  Particle* ptr_p, int num, ParticleBehavior behavior   ){

	}
	void Update_Cone( GLdouble time,  Particle* ptr_p, int num, ParticleBehavior behavior   ){

	}
	void Update_Explode( GLdouble time, Particle* ptr_p, int num, ParticleBehavior behavior   ){

	}

	// Update our living pools of 32 particles.
	void Update_32( GLdouble time ){
		ParticlePool_32* ref_pool = 0;
		for( int c = 0; c < lppool_32.size(); c++ ){
			ref_pool = lppool_32.at( c )->pool32;
			if( ref_pool->behavior.behavior_type == LINEAR )
				Update_Linear( time, ref_pool->particle, ref_pool->position_buffer, 32, &ref_pool->numAlive,ref_pool->behavior );
			else if( ref_pool->behavior.behavior_type == OBRIT )
				Update_Orbit( time, ref_pool->particle, 32, ref_pool->behavior  );
			else if( ref_pool->behavior.behavior_type == CONESPRAY )
				Update_Cone( time, ref_pool->particle, 32, ref_pool->behavior  );
			else
				Update_Explode( time, ref_pool->particle, 32, ref_pool->behavior  );
		}
		ref_pool = 0;

		// Clear out dead pools.
		for( int c = 0; c < lppool_32.size(); c++ ){
			if( lppool_32.at( c )->pool32->numAlive <= 0 )
				lppool_32.erase( lppool_32.begin() + c );
		}
	}
	void Update( GLdouble time ){
		Update_32( time );
	}


	void SubmitParticleTickets(){

		//dbg.
		//printf("drawing [%d] live pools\n", lppool_32.size());

		// Draw any live pools.
		for( int c = 0; c < lppool_32.size(); c++ ){

			lppool_32.at( c )->rticket.distance = 1.0f;

			lppool_32.at( c )->rticket.shadargs=0;

			lppool_32.at( c )->rticket.ref_point_vertexBuffer = lppool_32.at( c )->pool32->position_buffer;
			lppool_32.at( c )->rticket.point_count = 32;

			RendererManager::accept_renderTicket_particle( lppool_32.at( c )->rticket );
		}
		
	}

};

const float ParticleManager::LIFETIME_INFINITE = 0.0f;